﻿///////////////////////////////////////////////////////////////////////////////
// Copyright 2016 lingzimaike@gmail.com
// GPLv2
// Author: lingzimaike
///////////////////////////////////////////////////////////////////////////////

#include "papa/application.h"
#include <cassert>
#include <exception>
#include <csignal>

namespace papa {

	Application* Application::sapp_ = NULL;

	Application::Application(void)
		: unhandled_arguments_()
		, option_set_()
        , logger_("default")
	{

	}

	Application::~Application(void)
	{

	}

	int Application::Init(int argc, char* argv[])
	{
		for (int arg = 0; arg < argc; ++arg) {
			unhandled_arguments_.push_back(argv[arg]);
		}
		if (!ProcessOptions()) {
			PrintUsage(option_set_);
			return EXIT_USAGE;
		}

		sapp_ = this;
		return Initialize();
	}

	int Application::Run(int argc, char* argv[])
	{
		int exit_code = Init(argc, argv);
		if(exit_code == EXIT_OK)
		{
			try
			{
				exit_code = EXIT_SOFTWARE;
				exit_code = Main(unhandled_arguments_);
			}
			catch (::std::exception& e) {
				HandleStdException(e);
			}
			catch (...) {
				HandleSystemException();
			}
		}
        Uninitialize();
		return exit_code;
	}

	void Application::HookSignal(int signal)
	{
		::signal(signal, &Application::SignalHandler);
	}

	void Application::UnhookSignal(int signal)
	{
		::signal(signal, NULL);
	}

	Application* Application::GetMe(void)
	{
		assert(sapp_ != NULL);
		return sapp_;
	}

    int Application::Initialize(void)
    {
        return EXIT_OK;
    }

    void Application::Uninitialize(void)
    {
        
    }

	void Application::DefineOptions(OptionSet& option_set)
	{

	}

	void Application::HandleOption(const char* name, const char* value)
	{

	}

	void Application::PrintUsage(OptionSet& option_set)
	{

	}

	void Application::HandleStdException(::std::exception& e)
	{

	}

	void Application::HandleSystemException(void)
	{

	}

	void Application::HandleSignal(int signal)
	{
		
	}

	bool Application::ProcessOptions(void)
	{
		DefineOptions(option_set_);
		option_set_.Process(unhandled_arguments_);
		const ::std::vector<Option>& options = option_set_.get_options();
		for (::std::vector<Option>::const_iterator it = options.begin(); 
			it != options.end(); ++it)
		{
			if (!it->is_valid())
			{
				if (it->is_required())
					return false;
			}
			else {
				HandleOption(
					it->get_long_name().c_str(), 
					it->get_value().c_str());
			}
		}
		return true;
	}

	void Application::SignalHandler(int signal)
	{
		Application::GetMe()->HandleSignal(signal);
	}

	Application::Option::Option(const char* long_name, const char* short_name, const char* help_string)
		: long_name_(long_name)
		, short_name_(short_name)
		, help_string_(help_string)
		, required_(false)
		, has_argument_(false)
		, value_("")
		, valid_(false)
	{

	}

	Application::Option::~Option(void)
	{

	}

	bool Application::Option::MatchLongName(const ::std::string& name)
	{
		if (name.length() > 2)
		{
			return ( (name[0] == '-') && (name[1] == '-') 
				&& (long_name_.compare(&name[2]) == 0) );
		}
		return false;
	}

	bool Application::Option::MatchShortName(const ::std::string& name)
	{
		if (name.length() > 1)
		{
			return ( (name[0] == '-') 
				&& (short_name_.compare(&name[1]) == 0) );
		}
		return false;
	}

	Application::OptionSet::OptionSet(void)
		: options_()
	{

	}

	Application::OptionSet::~OptionSet(void)
	{

	}

	void Application::OptionSet::AddOption(Option& option)
	{
		for (::std::vector<Option>::const_iterator it = options_.begin(); 
			it != options_.end(); ++it)
		{
			if ( (it->get_long_name() == option.get_long_name()) 
				&& (it->get_short_name() == option.get_short_name()) ) {
					return;
			}
		}
		options_.push_back(option);
	}

	void Application::OptionSet::Process(::std::vector<std::string>& arguments)
	{
        if (options_.size() == 0) {
            return;
        }

		::std::vector<std::string>::const_iterator argument = arguments.begin();
		for (; argument != arguments.end();)
		{
			::std::vector<Option>::iterator option = options_.begin();
			for (; (option != options_.end() && argument != arguments.end()); ++option)
			{
				if (option->MatchLongName(*argument) || option->MatchShortName(*argument))
				{
					argument = arguments.erase(argument);
					if (option->has_argument() && argument != arguments.end())
					{
						option->set_value(*argument);
						option->is_valid(true);
						argument = arguments.erase(argument);
					}
					else if (!option->has_argument()) {
						option->is_valid(true);
					}
				}
				else {
					// unknown argument
					++argument;
				}
			}
		}
	}
}  // namespace papa